/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt.inspections;

import com.intellij.codeInspection.*;
import com.intellij.gwt.GwtConfiguration;
import com.intellij.gwt.GwtBundle;
import com.intellij.gwt.module.GwtModulesManager;
import com.intellij.gwt.module.model.GwtModule;
import com.intellij.gwt.references.GwtToCssClassReference;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.psi.*;
import com.intellij.psi.css.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.ArrayList;

public class GwtToCssClassReferencesInspection extends LocalInspectionTool {
  private static final Logger LOG = Logger.getInstance("#com.intellij.gwt.inspections.GwtToCssClassReferencesInspection");

  @Nullable
  public ProblemDescriptor[] checkMethod(@NotNull PsiMethod method, @NotNull final InspectionManager manager, boolean isOnTheFly) {
    PsiCodeBlock body = method.getBody();
    if (body == null || !GwtConfiguration.getInstance().isValidConfiguration()) return null;

    final CssReferencesProblemsCollectingVisitor collectingVisitor = new CssReferencesProblemsCollectingVisitor(manager);
    collectingVisitor.visitElement(body);

    return collectingVisitor.getProblems();
  }

  @NotNull
  public String getGroupDisplayName() {
    return GwtInspectionsProvider.GWT_INSPECTIONS_GROUP;
  }

  @NotNull
  public String getDisplayName() {
    return GwtBundle.message("inspection.unresolved.references.to.css.classes.display.name");
  }

  @NotNull
  @NonNls
  public String getShortName() {
    return "GWTStyleCheck";
  }

  @NotNull
  public HighlightDisplayLevel getDefaultLevel() {
    return HighlightDisplayLevel.ERROR;
  }

  public boolean isEnabledByDefault() {
    return true;
  }

  private static class MyLocalQuickFix implements LocalQuickFix {
    private final PsiLiteralExpression myExpression;
    private CssFile myCssFile;

    public MyLocalQuickFix(PsiLiteralExpression expression, final CssFile cssFile) {
      myExpression = expression;
      myCssFile = cssFile;
    }

    @NotNull
    public String getName() {
      return GwtBundle.message("quick.fix.create.css.class.name", myExpression.getText());
    }

    @NotNull
    public String getFamilyName() {
      return GwtInspectionsProvider.GWT_QUICKFIXES_FAMILY;
    }

    public void applyFix(@NotNull Project project, ProblemDescriptor descriptor) {
      if (ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(myCssFile.getVirtualFile()).hasReadonlyFiles()) {
        return;
      }

      try {
        CssStylesheet stylesheet = myCssFile.getStylesheet();
        String className = String.valueOf(myExpression.getValue());
        CssRuleset cssRuleset = CssElementFactory.getInstance(project).createRuleset("." + className + "{\n\n}\n");
        stylesheet.addRuleset(cssRuleset);
        final CssRuleset[] rulesets = stylesheet.getRulesets();
        final CssRuleset added = rulesets[rulesets.length - 1];
        final OpenFileDescriptor fileDescriptor = new OpenFileDescriptor(project, myCssFile.getVirtualFile(), added.getBlock().getTextOffset());
        FileEditorManager.getInstance(project).openEditor(fileDescriptor, true);
      }
      catch (IncorrectOperationException e) {
        LOG.error(e);
      }
    }
  }

  private static class CssReferencesProblemsCollectingVisitor extends PsiRecursiveElementVisitor {
    private final InspectionManager myManager;
    private final List<ProblemDescriptor> myProblems = new ArrayList<ProblemDescriptor>();

    public CssReferencesProblemsCollectingVisitor(InspectionManager manager) {
      myManager = manager;
    }

    public void visitLiteralExpression(PsiLiteralExpression expression) {
      final GwtToCssClassReference reference = findUnresolvedReference(expression);
      if (reference != null) {
        addProblem(expression, reference);
      }
    }

    private static GwtToCssClassReference findUnresolvedReference(final PsiLiteralExpression expression) {
      final PsiReference[] references = expression.getReferences();
      for (PsiReference reference : references) {
        if (reference instanceof GwtToCssClassReference && reference.resolve() == null) {
          return (GwtToCssClassReference)reference;
        }
      }
      return null;
    }

    private void addProblem(final PsiLiteralExpression expression, final GwtToCssClassReference reference) {
      MyLocalQuickFix fix = null;
      final GwtModule module = reference.findGwtModule();
      if (module != null) {
        CssFile cssFile = GwtModulesManager.getInstance(expression.getProject()).findPreferableCssFile(module);
        if (cssFile != null) {
          fix = new MyLocalQuickFix(expression, cssFile);
        }
      }
      myProblems.add(myManager.createProblemDescriptor(expression, GwtBundle.message("problem.description.unknown.css.class", expression.getValue()),
                                                       fix, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL));
    }

    public ProblemDescriptor[] getProblems() {
      return myProblems.toArray(new ProblemDescriptor[myProblems.size()]);
    }

    public void visitReferenceExpression(PsiReferenceExpression expression) {
    }
  }
}
